using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

using OpenSim.Region.ClientStack.FunSLUDP;

// See, no libsecondlife :)

class FunSL
{
    const int NUM_PACKETS=10;
    const int PACKET_SIZE=1024;

    private byte[] zerobuffer=new byte[PACKET_SIZE];
    private Queue free_udp_packets=new Queue();

    private Socket udp_socket;
    private IPEndPoint socket_ep;

    public FunSL()
    {
        InitPacketPool();
    }

    private static System.Security.Cryptography.MD5 MD5Builder = new System.Security.Cryptography.MD5CryptoServiceProvider();
    private static readonly System.Globalization.CultureInfo EnUsCulture = new System.Globalization.CultureInfo("en-us");

    public static string MD5(string password)
    {
        byte[] hash;
        StringBuilder digest = new StringBuilder();
        lock(MD5Builder) hash = MD5Builder.ComputeHash(ASCIIEncoding.Default.GetBytes(password));

        // Convert the hash to a hex string
        foreach (byte b in hash) digest.AppendFormat(EnUsCulture, "{0:x2}", b);

        return "$1$" + digest.ToString();
    }

    public void Login(string url, string first_name, string last_name, string password, out uint code, out string sessionID, out string agentID)
    {
        // Prepare message (LL XML)
        string param=String.Format("<llsd><map><key>mac</key><string>00:00:00:00:00:00</string><key>first</key><string>{0}</string><key>id0</key><string>00:00:00:00:00:00</string><key>options</key><array><string>inventory-root</string><string>inventory-skeleton</string><string>inventory-lib-root</string><string>inventory-lib-owner</string><string>inventory-skel-lib</string><string>buddy-list</string></array><key>read_critical</key><boolean>1</boolean><key>last</key><string>{1}</string><key>viewer_digest</key><string /><key>channel</key><string>My First Bot (libsecondlife)</string><key>version</key><string>Your name</string><key>platform</key><string>Linux</string><key>agree_to_tos</key><boolean>1</boolean><key>passwd</key><string>{2}</string><key>start</key><string>last</string></map></llsd>", first_name, last_name, MD5(password));

        // Send the message to the user server
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(url));
        request.Method = "POST";
        request.ContentType = "application/xml+llsd";
        byte[] data=ASCIIEncoding.Default.GetBytes(param);
        request.ContentLength = data.Length;
        request.GetRequestStream().Write(data, 0, data.Length);  // only works for small data (<4096)

        // Read the reply
        int size=0;
        WebResponse response = request.GetResponse();
        byte[] buffer = new byte[response.ContentLength];
        Stream stream=response.GetResponseStream();
        while(size<response.ContentLength) size += stream.Read(buffer, size, (int) (response.ContentLength-size));
        string reply=ASCIIEncoding.Default.GetString(buffer);

        // Extract important information
        code=Convert.ToUInt32(new Regex(@"<key>circuit_code</key><integer>(.*?)</integer>").Match(reply).Groups[1].Value);
        sessionID=new Regex(@"<key>session_id</key><uuid>(.*?)</uuid>").Match(reply).Groups[1].Value;
        agentID=new Regex(@"<key>agent_id</key><uuid>(.*?)</uuid>").Match(reply).Groups[1].Value;
    }

    public void Connect(string url)
    {
        // Parse the url
        Match m = Regex.Match(url, @"http://(.*):(.*)");
        if(!m.Success) {
            Console.WriteLine("Bad url format in Connect");
            System.Environment.Exit(-1);
        }
        string ip=m.Groups[1].Value;
        int port=Convert.ToInt32(m.Groups[2].Value);

        // TODO: remove me (find a better method)
        if(ip=="localhost") ip="127.0.0.1";

        socket_ep = new IPEndPoint(IPAddress.Parse(ip), port);
        udp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    }

    public void Disconnect()
    {
        udp_socket.Close();
    }

    // Will return the new length, and update the packet contents
    public int ZeroEncodePacket(byte[] packet, int length)
    {
        Buffer.BlockCopy(packet, 0, zerobuffer, 0, length);
        return ZeroEncode(zerobuffer, length, packet);
    }

    // Will return the new length, and update the packet contents
    public int ZeroDecodePacket(byte[] packet, int length)
    {
        Buffer.BlockCopy(packet, 0, zerobuffer, 0, length);
        return ZeroDecode(zerobuffer, length, packet);
    }

    // Copied from libsecondlife, Helpers.cs
    private int ZeroEncode(byte[] src, int srclen, byte[] dest)
    {
        uint zerolen = 0;
        byte zerocount = 0;

        Buffer.BlockCopy(src, 0, dest, 0, 6);
        zerolen += 6;

        int bodylen;
        if (!SLPacket.Header.HasAcks(src))
        {
            bodylen = srclen;
        }
        else
        {
            bodylen = srclen - src[srclen - 1] * 4 - 1;
        }

        uint i;
        for (i = zerolen; i < bodylen; i++)
        {
            if (src[i] == 0x00)
            {
                zerocount++;

                if (zerocount == 0)
                {
                    dest[zerolen++] = 0x00;
                    dest[zerolen++] = 0xff;
                    zerocount++;
                }
            }
            else
            {
                if (zerocount != 0)
                {
                    dest[zerolen++] = 0x00;
                    dest[zerolen++] = (byte)zerocount;
                    zerocount = 0;
                }

                dest[zerolen++] = src[i];
            }
        }

        if (zerocount != 0)
        {
            dest[zerolen++] = 0x00;
            dest[zerolen++] = (byte)zerocount;
        }

        // copy appended ACKs
        for (; i < srclen; i++)
        {
            dest[zerolen++] = src[i];
        }

        return (int)zerolen;
    }


    // Copied from libsecondlife, Helpers.cs
    public int ZeroDecode(byte[] src, int srclen, byte[] dest)
    {
        uint zerolen = 0;
        int bodylen = 0;
        uint i = 0;

        try
        {
            Buffer.BlockCopy(src, 0, dest, 0, 6);
            zerolen = 6;
            bodylen = srclen;

            for (i = zerolen; i < bodylen; i++)
            {
                if (src[i] == 0x00)
                {
                    for (byte j = 0; j < src[i + 1]; j++)
                    {
                        dest[zerolen++] = 0x00;
                    }

                    i++;
                }
                else
                {
                    dest[zerolen++] = src[i];
                }
            }

            // Copy appended ACKs
            for (; i < srclen; i++)
            {
                dest[zerolen++] = src[i];
            }

            return (int)zerolen;
        }
        catch (Exception e)
        {
            Console.WriteLine("Zerodecoding error: " + e.ToString());
        }

        return 0;
    }

  /* Packet handling: UDP packets must be returned to avoid memory leaks and fragmentation */

    private void InitPacketPool()
    {
        lock(free_udp_packets) 
        {
            for(int i=0; i<NUM_PACKETS; i++) {
                free_udp_packets.Enqueue(new byte[PACKET_SIZE]);
            }
        }
    }

    public byte[] GetPacket()
    {
        byte[] packet=null;
        lock(free_udp_packets) 
        {
            if(free_udp_packets.Count==0) {
                Console.WriteLine("Out of packets error");
                System.Environment.Exit(-1);
            }
            packet=(byte[]) free_udp_packets.Dequeue();
        }
        return packet;
    }

    public void ReturnPacket(byte[] packet)
    {
        lock(free_udp_packets) 
        {
            free_udp_packets.Enqueue(packet);
        }
    }
 

    public void SendOutgoingPacket(byte[] packet, int length)
    {
        if (SLPacket.Header.IsZerocoded(packet))
        {
            length=ZeroEncodePacket(packet, length);
        }
        udp_socket.SendTo(packet, length, SocketFlags.None, socket_ep);
    }

    public byte[] GetIncomingPacket(byte[] packet, out int length, int timeout)
    {
        EndPoint ep=(EndPoint) socket_ep;
        length=udp_socket.ReceiveFrom(packet, ref ep);
        if (SLPacket.Header.IsZerocoded(packet))
        {
            length=ZeroDecodePacket(packet, length);
        }
        return packet;
    }
}
 
